package ccm.component.image;

import ccm.common.CCMConfigManager;
import ccm.common.dragMouse.DragMouse;
import ccm.common.dragMouse.DragMouseListener;
import ccm.component.override.JCheckBox;
import ccm.component.override.JLabel;
import ccm.component.override.JSpinner;
import ccm.component.override.VFlowLayout;

import javax.swing.*;
import javax.swing.event.ChangeEvent;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.util.ArrayList;

public class AdvancedResizeableImageJPanel extends ManualResizeableImageJPanel implements DragMouseListener, ActionListener
{

    protected final DragMouse dragMouse;
    protected final JSpinner biasXJSpinner;
    protected final JSpinner biasYJSpinner;
    protected final JSpinner lineXJSpinner;
    protected final JSpinner lineYJSpinner;
    protected final JCheckBox lineJCheckBox;
    protected final JPanel pointsJPanel;
    protected final ArrayList<Point> origin;
    protected final String saveArgPrefix;
    long lastMillSecond=0;

    public AdvancedResizeableImageJPanel()
    {
        this(null);
    }

    public AdvancedResizeableImageJPanel(BufferedImage image)
    {
        this(image,null);
    }

    public AdvancedResizeableImageJPanel(BufferedImage image,String saveArgPrefix)
    {
        super(image);
        this.saveArgPrefix=saveArgPrefix==null?"":(saveArgPrefix.endsWith("/")?saveArgPrefix:(saveArgPrefix+"/"));
        origin=new ArrayList<Point>();
        /*
         * 生成的X轴偏移面板
         */
        JPanel biasXJSpinnerJPanel=new JPanel();
        infoJPanel.add(biasXJSpinnerJPanel);
        biasXJSpinnerJPanel.setLayout(new FlowLayout(FlowLayout.LEFT));
        biasXJSpinnerJPanel.add(new JLabel("X偏移"));
        biasXJSpinner=new JSpinner(new SpinnerNumberModel(0,0,1000,1));
        biasXJSpinnerJPanel.add(biasXJSpinner);
        /*
         * 生成的Y轴偏移面板
         */
        JPanel biasYJSpinnerJPanel=new JPanel();
        infoJPanel.add(biasYJSpinnerJPanel);
        biasYJSpinnerJPanel.setLayout(new FlowLayout(FlowLayout.LEFT));
        biasYJSpinnerJPanel.add(new JLabel("Y偏移"));
        biasYJSpinner=new JSpinner(new SpinnerNumberModel(0,0,1000,1));
        biasYJSpinnerJPanel.add(biasYJSpinner);
        lineJCheckBox=new JCheckBox("辅助线");
        infoJPanel.add(lineJCheckBox);

        /*
         * 生成的X轴偏移面板
         */
        JPanel lineXJSpinnerJPanel=new JPanel();
        infoJPanel.add(lineXJSpinnerJPanel);
        lineXJSpinnerJPanel.setLayout(new FlowLayout(FlowLayout.LEFT));
        lineXJSpinnerJPanel.add(new JLabel("辅助点X"));
        lineXJSpinner=new JSpinner(new SpinnerNumberModel(0,0,1000,1));
        lineXJSpinnerJPanel.add(lineXJSpinner);
        /*
         * 生成的Y轴偏移面板
         */
        JPanel lineYJSpinnerJPanel=new JPanel();
        infoJPanel.add(lineYJSpinnerJPanel);
        lineYJSpinnerJPanel.setLayout(new FlowLayout(FlowLayout.LEFT));
        lineYJSpinnerJPanel.add(new JLabel("辅助点Y"));
        lineYJSpinner=new JSpinner(new SpinnerNumberModel(0,0,1000,1));
        lineYJSpinnerJPanel.add(lineYJSpinner);

        lineJCheckBox.setSelected(CCMConfigManager.setBooleanIfAbsent(this.saveArgPrefix+"ccm/component/image/AdvancedResizeableImageJPanel/line",true));
        biasXJSpinner.setValue(CCMConfigManager.setIntegerIfAbsent(this.saveArgPrefix+"ccm/component/image/AdvancedResizeableImageJPanel/biasXJSpinner",0));
        biasYJSpinner.setValue(CCMConfigManager.setIntegerIfAbsent(this.saveArgPrefix+"ccm/component/image/AdvancedResizeableImageJPanel/biasYJSpinner",0));
        lineXJSpinner.setValue(CCMConfigManager.setIntegerIfAbsent(this.saveArgPrefix+"ccm/component/image/AdvancedResizeableImageJPanel/lineXJSpinner",0));
        lineYJSpinner.setValue(CCMConfigManager.setIntegerIfAbsent(this.saveArgPrefix+"ccm/component/image/AdvancedResizeableImageJPanel/lineYJSpinner",0));
        resizeJSpinner.setValue(CCMConfigManager.setFloatIfAbsent(this.saveArgPrefix+"ccm/component/image/AdvancedResizeableImageJPanel/resizeJSpinner",1));

        biasXJSpinner.addChangeListener(this);
        biasYJSpinner.addChangeListener(this);
        lineXJSpinner.addChangeListener(this);
        lineYJSpinner.addChangeListener(this);
        lineJCheckBox.addActionListener(this);

        dragMouse=new DragMouse(this,resizeJSpinner,null,biasYJSpinner,null,null,null,biasXJSpinner,null);
        imgJLabel.addMouseListener(dragMouse);
        imgJLabel.addMouseMotionListener(dragMouse);
        imgJLabel.addMouseWheelListener(dragMouse);
        addKeyListener(this);
        ((JSpinner.DefaultEditor)biasXJSpinner.getEditor()).getTextField().addKeyListener(this);
        ((JSpinner.DefaultEditor)biasYJSpinner.getEditor()).getTextField().addKeyListener(this);
        pointsJPanel=new JPanel();
        pointsJPanel.setLayout(new VFlowLayout());
        add(pointsJPanel,BorderLayout.WEST);
        {
            int len=CCMConfigManager.setIntegerIfAbsent(this.saveArgPrefix+"ccm/component/image/AdvancedResizeableImageJPanel/pointCnt",0);
            for(int i=0;i<len;++i)
            {
                int x=CCMConfigManager.getInteger(this.saveArgPrefix+"ccm/component/image/AdvancedResizeableImageJPanel/point"+i+"/x");
                int y=CCMConfigManager.getInteger(this.saveArgPrefix+"ccm/component/image/AdvancedResizeableImageJPanel/point"+i+"/y");
                origin.add(new Point(x,y));
            }
        }
    }
    public void addKeyListener(KeyListener keyListener)
    {
        biasXJSpinner.addKeyListener(keyListener);
        ((JSpinner.DefaultEditor)biasXJSpinner.getEditor()).getTextField().addKeyListener(keyListener);
        biasYJSpinner.addKeyListener(keyListener);
        ((JSpinner.DefaultEditor)biasYJSpinner.getEditor()).getTextField().addKeyListener(keyListener);
        lineXJSpinner.addKeyListener(keyListener);
        ((JSpinner.DefaultEditor)lineXJSpinner.getEditor()).getTextField().addKeyListener(keyListener);
        lineYJSpinner.addKeyListener(keyListener);
        ((JSpinner.DefaultEditor)lineYJSpinner.getEditor()).getTextField().addKeyListener(keyListener);
        lineJCheckBox.addKeyListener(keyListener);
    }
    /**
     * 在手动设置缩放的输入框中值发生变化时自动更新缩放
     */
    @Override
    public void stateChanged(ChangeEvent e)
    {
        CCMConfigManager.setInteger(this.saveArgPrefix+"ccm/component/image/AdvancedResizeableImageJPanel/biasXJSpinner",(int)biasXJSpinner.getValue());
        CCMConfigManager.setInteger(this.saveArgPrefix+"ccm/component/image/AdvancedResizeableImageJPanel/biasYJSpinner",(int)biasYJSpinner.getValue());
        CCMConfigManager.setInteger(this.saveArgPrefix+"ccm/component/image/AdvancedResizeableImageJPanel/lineXJSpinner",(int)lineXJSpinner.getValue());
        CCMConfigManager.setInteger(this.saveArgPrefix+"ccm/component/image/AdvancedResizeableImageJPanel/lineYJSpinner",(int)lineYJSpinner.getValue());
    }

    public void keyPressed(KeyEvent e)
    {
        super.keyPressed(e);
        ((JSpinner.DefaultEditor)(biasXJSpinner).getEditor()).getTextField().setEditable(!(isPreventKey(e.getKeyCode())));
        ((JSpinner.DefaultEditor)(biasYJSpinner).getEditor()).getTextField().setEditable(!(isPreventKey(e.getKeyCode())));
        ((JSpinner.DefaultEditor)(lineXJSpinner).getEditor()).getTextField().setEditable(!(isPreventKey(e.getKeyCode())));
        ((JSpinner.DefaultEditor)(lineYJSpinner).getEditor()).getTextField().setEditable(!(isPreventKey(e.getKeyCode())));
        if(e.getKeyCode()==KeyEvent.VK_Q)
        {
            final int x=(int)((dragMouse.getMouseData().getPoint().getX()+(Integer)biasXJSpinner.getValue())/resizeRate);
            final int y=(int)((dragMouse.getMouseData().getPoint().getY()+(Integer)biasYJSpinner.getValue())/resizeRate);
            int len=origin.size();
            origin.removeIf(p->x==p.getX()&&y==p.getY());
            if(len==origin.size())
            {
                origin.add(new Point(x,y));
            }
            CCMConfigManager.setInteger(this.saveArgPrefix+"ccm/component/image/AdvancedResizeableImageJPanel/pointCnt",origin.size());
            for(int i=0;i<origin.size();++i)
            {
                CCMConfigManager.setInteger(this.saveArgPrefix+"ccm/component/image/AdvancedResizeableImageJPanel/point"+i+"/x",origin.get(i).x);
                CCMConfigManager.setInteger(this.saveArgPrefix+"ccm/component/image/AdvancedResizeableImageJPanel/point"+i+"/y",origin.get(i).y);
            }
            updateJLabel(dragMouse.getMouseData().getPoint());
        }
        else if(e.getKeyCode()==KeyEvent.VK_E)
        {
            origin.clear();
            CCMConfigManager.setInteger(this.saveArgPrefix+"ccm/component/image/AdvancedResizeableImageJPanel/pointCnt",origin.size());
            updateJLabel(dragMouse.getMouseData().getPoint());
        }
    }

    protected void updateJLabel(Point p)
    {
        if(image==null||resizeRate<=0)
        {
            return;
        }
        final int x=(int)((p.getX()+(Integer)biasXJSpinner.getValue())/resizeRate);
        final int y=(int)((p.getY()+(Integer)biasYJSpinner.getValue())/resizeRate);
        if(x>=0&&y>=0&&x<image.getWidth()&&y<image.getHeight())
        {
            pointsJPanel.removeAll();
            final BufferedImage tmp=new BufferedImage(100,10,BufferedImage.TYPE_INT_RGB);
            for(int i=0;i<100;++i)
            {
                for(int j=0;j<10;++j)
                {
                    tmp.setRGB(i,j,image.getRGB(x,y));
                }
            }
            pointsJPanel.add(new JLabel(new ImageIcon(tmp)));
            pointsJPanel.add(new JLabel(String.format("0X%08X",image.getRGB(x,y))));
            pointsJPanel.add(new JLabel("("+0+','+0+")=>("+x+','+y+')'));
            for(Point o: origin)
            {
                pointsJPanel.add(new JLabel('('+(int)o.getX()+','+(int)o.getY()+")=>("+(x-(int)o.getX())+','+(y-(int)o.getY())+')'));
            }
            pointsJPanel.repaint();
        }
    }

    @Override
    public void dragMouseRepaint(MouseEvent e)
    {
        updateResizeRate();
        updateImg();
        revalidate();
        updateJLabel(new Point(e.getX(),e.getY()));
    }

    /**
     * 更新缩放的比例
     */
    protected void updateResizeRate()
    {
        resizeRate=maxResizeRate;
    }

    /**
     * 设置图片
     *
     * @param image 图片
     */
    public void setImage(BufferedImage image)
    {
        super.setImage(image);
        lastMillSecond=0;
    }

    public BufferedImage getScaledInstance(BufferedImage cu)
    {
        final int w=(int)(cu.getWidth()*resizeRate);
        final int h=(int)(cu.getHeight()*resizeRate);
        final Image ti=cu.getScaledInstance(w,h,Image.SCALE_FAST);
        BufferedImage newImage=new BufferedImage(w,h,cu.getType());
        Graphics2D g=newImage.createGraphics();
        g.drawImage(ti,0,0,null);
        g.dispose();
        return newImage;
    }

    /**
     * 更新图片并处理缩放
     */
    protected synchronized void updateImg()
    {
        if(image==null||resizeRate<=0)
        {
            return;
        }
        if(lastMillSecond+20>System.currentTimeMillis())
        {
            return;
        }

        maxResizeRate=resizeRate=((Number)resizeJSpinner.getValue()).floatValue();
        CCMConfigManager.setFloat(this.saveArgPrefix+"ccm/component/image/AdvancedResizeableImageJPanel/resizeJSpinner",resizeRate);
        updateBiasXJSpinner();
        updateBiasYJSpinner();
        updateLineXJSpinner();
        updateLineYJSpinner();

        final int width=(int)(getDisplayWidth()/resizeRate);
        final int height=(int)(getDisplayHeight()/resizeRate);
        if(width!=0&&height!=0)
        {
            float maxResizeRate=Math.min(1.0f*getDisplayWidth()/image.getWidth(),1.0f*getDisplayHeight()/image.getHeight());
            if(resizeRate>=maxResizeRate)
            {
                final int cut1W=(int)Math.ceil(Math.min(image.getWidth()*resizeRate,getDisplayWidth())/resizeRate);
                final int cut1H=(int)Math.ceil(Math.min(image.getHeight()*resizeRate,getDisplayHeight())/resizeRate);
                final int cut1X=(int)Math.floor((Integer)biasXJSpinner.getValue()/resizeRate);
                final int cut1Y=(int)Math.floor((Integer)biasYJSpinner.getValue()/resizeRate);
                final BufferedImage ti=getScaledInstance(image.getSubimage(cut1X,cut1Y,cut1W,cut1H));
                if(resizeRate>=2&&lineJCheckBox.isSelected())
                {
                    for(int i=0;i<ti.getWidth()/resizeRate;++i)
                    {
                        for(int j=0;j<ti.getHeight();++j)
                        {
                            ti.setRGB((int)(i*resizeRate),j,0XDDDDDD);
                        }
                    }
                    for(int i=0;i<ti.getWidth();++i)
                    {
                        for(int j=0;j<ti.getHeight()/resizeRate;++j)
                        {
                            ti.setRGB(i,(int)(j*resizeRate),0XDDDDDD);
                        }
                    }
                    if(dragMouse.getMouseData().getPoint()!=null)
                    {
                        final int ox=(int)((dragMouse.getMouseData().getPoint().getX()+(Integer)biasXJSpinner.getValue())/resizeRate);
                        final int oy=(int)((dragMouse.getMouseData().getPoint().getY()+(Integer)biasYJSpinner.getValue())/resizeRate);
                        final int r=(int)Math.ceil(resizeRate);
                        {
                            final int x=ox;
                            final int y=oy;
                            final int xx=(int)((x-cut1X)*resizeRate);
                            final int yy=(int)((y-cut1Y)*resizeRate);
                            if(xx>=0&&yy>=0&&xx+r<ti.getWidth()&&yy+r<ti.getHeight())
                            {
                                for(int i=0;i<r;++i)
                                {
                                    ti.setRGB(xx,yy+i,~image.getRGB(x,y));
                                    ti.setRGB(xx+r,yy+i,~image.getRGB(x,y));
                                    ti.setRGB(xx+i,yy,~image.getRGB(x,y));
                                    ti.setRGB(xx+i,yy+r,~image.getRGB(x,y));
                                }
                            }
                        }
                        if(0!=(Integer)lineXJSpinner.getValue()||0!=(Integer)lineYJSpinner.getValue())
                        {
                            final int x=ox-(Integer)lineXJSpinner.getValue();
                            final int y=oy-(Integer)lineYJSpinner.getValue();
                            final int xx=(int)((x-cut1X)*resizeRate);
                            final int yy=(int)((y-cut1Y)*resizeRate);
                            if(xx>=0&&yy>=0&&xx+r<ti.getWidth()&&yy+r<ti.getHeight())
                            {
                                for(int i=0;i<r;++i)
                                {
                                    ti.setRGB(xx,yy+i,~image.getRGB(x,y));
                                    ti.setRGB(xx+r,yy+i,~image.getRGB(x,y));
                                    ti.setRGB(xx+i,yy,~image.getRGB(x,y));
                                    ti.setRGB(xx+i,yy+r,~image.getRGB(x,y));
                                }
                            }
                        }
                    }
                }

                final int cut2X=(Integer)biasXJSpinner.getValue()-(int)(cut1X*resizeRate);
                final int cut2Y=(Integer)biasYJSpinner.getValue()-(int)(cut1Y*resizeRate);
                final int cut2W=Math.min(ti.getWidth()-cut2X,(int)((int)Math.floor(Math.min(image.getWidth()*resizeRate,getDisplayWidth())/resizeRate)*resizeRate));
                final int cut2H=Math.min(ti.getHeight()-cut2Y,(int)((int)Math.floor(Math.min(image.getHeight()*resizeRate,getDisplayHeight())/resizeRate)*resizeRate));
                final BufferedImage cut2=ti.getSubimage(cut2X,cut2Y,cut2W,cut2H);
                imgJLabel.setIcon(new ImageIcon(cut2));
            }
            else
            {
                imgJLabel.setIcon(new ImageIcon(image.getScaledInstance((int)(image.getWidth()*resizeRate),(int)(image.getHeight()*resizeRate),Image.SCALE_FAST)));
            }
        }
        else
        {
            imgJLabel.setIcon(new ImageIcon(image));
        }
        lastMillSecond=System.currentTimeMillis();
        if(pointsJPanel.getComponentCount()==0)
        {
            updateJLabel(new Point(0,0));
        }
    }

    protected void updateBiasXJSpinner()
    {
        if(image==null||resizeRate<=0)
        {
            return;
        }
        SpinnerNumberModel spinnerNumberModel=((SpinnerNumberModel)biasXJSpinner.getModel());
        int value=(int)biasXJSpinner.getValue();
        int valueMax=Math.max(0,(int)(image.getWidth()*resizeRate-getDisplayWidth()));
        spinnerNumberModel.setValue(Math.min(valueMax,value));
        spinnerNumberModel.setMaximum(valueMax);
    }

    protected void updateBiasYJSpinner()
    {
        if(image==null||resizeRate<=0)
        {
            return;
        }
        SpinnerNumberModel spinnerNumberModel=((SpinnerNumberModel)biasYJSpinner.getModel());
        int value=(int)biasYJSpinner.getValue();
        int valueMax=Math.max(0,(int)(image.getHeight()*resizeRate-getDisplayHeight()));
        spinnerNumberModel.setValue(Math.min(valueMax,value));
        spinnerNumberModel.setMaximum(valueMax);
    }

    protected void updateLineXJSpinner()
    {
        if(image==null||resizeRate<=0)
        {
            return;
        }
        SpinnerNumberModel spinnerNumberModel=((SpinnerNumberModel)lineXJSpinner.getModel());
        int value=(int)lineXJSpinner.getValue();
        int valueMax=Math.max(0,(int)(image.getWidth()*resizeRate-getDisplayWidth()));
        spinnerNumberModel.setValue(Math.min(valueMax,value));
        spinnerNumberModel.setMaximum(valueMax);
    }

    protected void updateLineYJSpinner()
    {
        if(image==null||resizeRate<=0)
        {
            return;
        }
        SpinnerNumberModel spinnerNumberModel=((SpinnerNumberModel)lineYJSpinner.getModel());
        int value=(int)lineYJSpinner.getValue();
        int valueMax=Math.max(0,(int)(image.getHeight()*resizeRate-getDisplayHeight()));
        spinnerNumberModel.setValue(Math.min(valueMax,value));
        spinnerNumberModel.setMaximum(valueMax);
    }

    int getDisplayWidth()
    {
        return getWidth()-pointsJPanel.getWidth();
    }

    int getDisplayHeight()
    {
        return getHeight()-infoJPanel.getHeight();
    }

    /**
     * 鼠标点击时向上触发点击事件
     */
    public void mouseClicked(MouseEvent e)
    {
        if(e.getSource().equals(imgJLabel))
        {
            final int x=(int)((e.getX()+(Integer)biasXJSpinner.getValue())/resizeRate);
            final int y=(int)((e.getY()+(Integer)biasYJSpinner.getValue())/resizeRate);
            imageJPanelListener.forEach(l->l.imageClicked(new ImageEvent(this,1,"clicked",x,y)));
        }
    }

    @Override
    public void actionPerformed(ActionEvent e)
    {
        updateResizeRate();
        updateImg();
        revalidate();
        CCMConfigManager.setBoolean(this.saveArgPrefix+"ccm/component/image/AdvancedResizeableImageJPanel/line",lineJCheckBox.isSelected());
    }

//
//    /**
//     * 用于保存参数
//     */
//    @Override
//    public void writeExternal(ObjectOutput out) throws IOException
//    {
//        out.writeInt(origin.size());
//        for(Point o: origin)
//        {
//            out.writeInt((int)o.getX());
//            out.writeInt((int)o.getY());
//        }
//        out.writeFloat(resizeRate);
//    }
}
